﻿using System;
using System.Text;
using System.IO;
using Browis.Utils;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
using log4net;

namespace Browis.Package
{
    /// <summary>
    /// 包文件类
    /// </summary>
    public class Package
    {
        /// <summary>
        /// 定义包头大小
        /// </summary>
        private const int PackageHeaderSize = 148;
        private const int StringSize = 64;

        private Stream writer;
        private long length;
        private UInt32 crc;
        private UInt32 dosTime;
        private Dictionary<String, Object> meta;
        private ILog log = null;

        public String FileName
        {
            get;
            private set;
        }

        public UInt32 CRC
        {
            get
            {
                if (writer.CanWrite == true)
                {
                    HashStream hs = new HashStream(writer, true, new CRC32());
                    Byte[] buffer = new Byte[4096];

                    long position = writer.Position;
                    //流的位置置为包头信息后，并用HashStream读取流内包头信息后的全部数据
                    writer.Position = PackageHeaderSize;
                    while (hs.Read(buffer, 0, buffer.Length) > 0) ;
                    buffer = hs.Checksum;
                    hs.Close();

                    writer.Position = position;

                    return BitConverter.ToUInt32(buffer, 0);
                }
                else
                    return crc;
            }
        }

        public long Length
        {
            get
            {
                if (writer.CanWrite == true)
                    return writer.Length;
                else
                    return length;
            }
        }

        public UInt32 HeaderLength
        {
            get;
            private set;
        }

        public DateTime DosTime
        {
            get
            {
                if (writer.CanWrite == true)
                    return DateTime.Now;
                else
                    return ToDateTime(dosTime);
            }
        }

        private static UInt32 GetDosTime(DateTime now)
        {
            return (UInt32)((now.Second / 2) | (now.Minute << 5) | (now.Hour << 11) | (now.Day << 16) | (now.Month << 21) | ((now.Year - 1980) << 25));
        }

        private static DateTime ToDateTime(UInt32 time)
        {
            return new DateTime((int)(time >> 25) + 1980, (int)(time >> 21) & 0xf, (int)(time >> 16) & 0x1f, (int)(time >> 11) & 0xf, (int)(time >> 5) & 0x3f, (int)(time & 0x1f) * 2);
        }

        /// <summary>
        /// 构造函数中生成元数据目录
        /// </summary>
        protected Package()
        {
            meta = new Dictionary<String, Object>();
            log = LogManager.GetLogger(this.GetType());
        }

        public static Package Open(string fileName, Stream writer)
        {
            Package instance = new Package();
            try
            {
                instance.FileName = fileName;
                instance.writer = writer;
                //检查包文件格式
                if (instance.writer.Length < PackageHeaderSize)
                {
                    instance.log.WarnFormat("包文件{0}长度异常！", fileName);
                    throw new FormatException(fileName);
                }
                byte[] buffer = new byte[PackageHeaderSize];
                instance.writer.Read(buffer, 0, buffer.Length);
                if (BitConverter.ToInt32(buffer, 0) != 0x53434349)
                {
                    instance.log.WarnFormat("包文件{0}头信息不正确！", fileName);
                    throw new FormatException(fileName);
                }
                instance.crc = BitConverter.ToUInt32(buffer, 4);
                instance.length = BitConverter.ToInt64(buffer, 8);
                instance.HeaderLength = BitConverter.ToUInt32(buffer, 16);
                instance.dosTime = BitConverter.ToUInt32(buffer, 20);

                if (instance.Length != instance.writer.Length)
                {
                    instance.log.WarnFormat("包文件{0}长度异常，可能已经被破坏！", fileName);
                    throw new Exception("流长度错误");
                }

                //对包进行CRC校验
                HashStream hs = new HashStream(instance.writer, true, new CRC32());
                instance.writer.Seek(instance.HeaderLength, SeekOrigin.Begin);

                buffer = new Byte[4096];
                while (hs.Read(buffer, 0, buffer.Length) > 0) ;
                buffer = hs.Checksum;
                hs.Close();

                if (BitConverter.ToUInt32(buffer, 0) != instance.CRC)
                {
                    instance.log.WarnFormat("包文件{0}的CRC校验错误，可能已经被破坏！", fileName);
                    throw new Exception("CRC校验错误");
                }
                instance.writer.Seek(instance.HeaderLength, SeekOrigin.Begin);

                //查找包中的META节
                SectionStream ss = instance.LookupSection("META");
                if (ss != null)
                {
                    Dictionary<String, Object> meta = new BinaryFormatter().Deserialize(ss) as Dictionary<String, Object>;
                    if (meta != null)
                        instance.meta = meta;
                    ss.Close();
                }
            }
            catch
            {
                writer.Close();
                throw;
            }
            return instance;
        }

        /// <summary>
        /// 创建包类
        /// </summary>
        /// <param name="fileName">包文件名</param>
        /// <param name="writer">包文件流</param>
        /// <returns>包类</returns>
        public static Package Create(string fileName, Stream writer)
        {
            Package instance = new Package();

            instance.FileName = fileName;
            //获取文件流，把流指针置于包头后
            instance.writer = writer;            
            instance.writer.SetLength(PackageHeaderSize);
            instance.writer.Seek(0, SeekOrigin.End);

            instance.HeaderLength = PackageHeaderSize;

            return instance;
        }


        /// <summary>
        /// 确认数据包的所有数据已经写出到流Writer中。
        /// </summary>
        private void Flush()
        {
            if (writer.CanWrite == false)
                return;

            byte[] buffer = new byte[StringSize];

            if (meta.Count != 0)
            {
                writer.Seek(0, SeekOrigin.End);

                SectionStream ss = CreateSection("META", 0);
                new BinaryFormatter().Serialize(ss, meta);
                ss.Close();
            }

            length = (UInt32)writer.Length;
            crc = CRC;
            dosTime = GetDosTime(DateTime.Now);

            writer.Seek(0, SeekOrigin.Begin);
            writer.Write(Encoding.ASCII.GetBytes("ICCS"), 0, 4);
            writer.Write(BitConverter.GetBytes(crc), 0, 4);
            writer.Write(BitConverter.GetBytes(writer.Length), 0, 8);
            writer.Write(BitConverter.GetBytes(PackageHeaderSize), 0, 4);
            writer.Write(BitConverter.GetBytes(dosTime), 0, 4);

        }

        public void Close()
        {
            Flush();
            writer.Close();
        }

        public Object GetMetaValue(String name)
        {
            if (meta.ContainsKey(name) == false)
                return null;
            else
                return meta[name];
        }

        public void SetMetaValue(String name, Object value)
        {
            meta[name] = value;
        }

        /// <summary>
        /// 创建节信息
        /// </summary>
        /// <param name="name"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public SectionStream CreateSection(String name, UInt32 options)
        {
            return SectionStream.Create(writer, name, options, log);
        }

        public SectionStream LookupSection(String name)
        {
            writer.Seek(PackageHeaderSize, SeekOrigin.Begin);
            return SectionStream.Lookup(writer, name, log);
        }
    }
}
